/**
 * 上传图片的方法
 * 参考自aisc: https://code.aone.alibaba-inc.com/aisc/aisc/blob/master/components/upload/runtime/request.jsx
 */

import assign from 'object-assign';
import mime from './mime';

function uid() {
  let now = +new Date();
  return (now++).toString(36);
}

function getError(option, xhr) {
  const msg = `cannot post ${option.action} ${xhr.status}'`;
  const err = new Error(msg);
  err.status = xhr.status;
  err.method = 'post';
  err.url = option.action;
  return err;
}

function getBody(xhr) {
  const text = xhr.responseText || xhr.response;
  if (!text) {
    return text;
  }

  try {
    return JSON.parse(text);
  } catch (e) {
    return text;
  }
}

function send(xhr, data) {
  if (typeof data === 'string') {
    // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/sendAsBinary
    if (xhr.sendAsBinary) {
      // Gecko
      xhr.sendAsBinary(data);
    } else {
      // WebKit with typed arrays support
      const ui8a = new Uint8Array(data.length);
      for (let i = 0, l = data.length; i < l; i++) {
        ui8a[i] = data.charCodeAt(i) & 0xff;
      }
      xhr.send(ui8a.buffer);
    }
  } else {
    xhr.send(data);
  }
}

export default function upload(option) {
  if (typeof XMLHttpRequest === 'undefined') {
    return;
  }

  const xhr = new XMLHttpRequest();
  if (xhr.upload) {
    xhr.upload.onprogress = function progress(e) {
      if (e.total > 0) {
        e.percent = (e.loaded / e.total) * 100;
      }
      option.onProgress(e);
    };
  }

  let formData;
  let fileName = (option.file && option.file.name) || '';

  if (option.filename === 'file') {
    // 上传文件，文件名要随机生成
    fileName = `${uid()}-${fileName}`;
    option.action = `${option.action}&file_name=${fileName}`;
  }

  const multipartArgs = assign(
    {
      name: fileName,
    },
    option.multipart,
  );
  if (typeof option.bin === 'string') {
    const { bin } = option;
    const boundary = `----ksuploadboundary${uid()}`;
    const dashdash = '--';
    const crlf = '\r\n';
    let multipartBlob = '';
    option.headers = option.headers || {};

    // if no FormData we can still try to send it directly as last resort (see below)
    // Trying to send the whole thing as binary...

    // Added multipart request
    option.headers['Content-Type'] = `multipart/form-data; boundary=${boundary}`;
    // append multipart parameters
    Object.keys(multipartArgs).forEach((name) => {
      /* eslint prefer-template: 0 */
      multipartBlob +=
        dashdash +
        boundary +
        crlf +
        'Content-Disposition: form-data; name="' +
        name +
        '"' +
        crlf +
        crlf;
      multipartBlob += unescape(encodeURIComponent(multipartArgs[name])) + crlf;
    });

    const mimeType =
      mime[option.file.name.replace(/^.+\.([^.]+)/, '$1').toLowerCase()] ||
      'application/octet-stream';

    // Build RFC2388 blob
    multipartBlob +=
      dashdash +
      boundary +
      crlf +
      'Content-Disposition: form-data; name="' +
      option.filename +
      '"; filename="' +
      unescape(encodeURIComponent(option.file.name)) +
      '"' +
      crlf +
      'Content-Type: ' +
      mimeType +
      crlf +
      crlf +
      bin +
      crlf +
      dashdash +
      boundary +
      dashdash +
      crlf;

    // multipartDeltaSize = multipartBlob.length - bin.length;
    formData = multipartBlob;
  } else {
    formData = new FormData();
    /* eslint-disable */
    Object.keys(multipartArgs).map((key) => {
      formData.set(key, multipartArgs[key]);
    });
    /* eslint-enable */
    formData.set(option.filename, option.bin);
  }

  xhr.onerror = function error(e) {
    option.onError(e);
  };

  xhr.onload = function onload() {
    if (xhr.status < 200 || xhr.status >= 300) {
      return option.onError(getError(option, xhr), getBody(xhr));
    }
    option.onSuccess(getBody(xhr));
  };

  xhr.onabort = function (e) {
    option.onAbort(e);
  };

  xhr.open('post', option.action, true);

  // Has to be after `.open()`. See https://github.com/enyo/dropzone/issues/179
  if (option.withCredentials && 'withCredentials' in xhr) {
    xhr.withCredentials = true;
  }

  const headers = option.headers || {};
  for (const h in headers) {
    // eslint-disable-next-line no-prototype-builtins
    if (headers.hasOwnProperty(h)) {
      xhr.setRequestHeader(h, headers[h]);
    }
  }
  send(xhr, formData);

  delete option.bin;
  delete option.file;

  return {
    abort() {
      xhr.abort();
    },
  };
}
